/**
 * @file 节点、组件搜寻
 * @author DuskyHuang 圣子
*/

import { Node, Component, Camera, find, director, renderer, Canvas } from 'cc';

// 相对路径
declare type relative = children | descendants | ancestors | self | root;
declare type ancestors = '..';              // 祖先节点
declare type children = '/.' | undefined;   // 子节点（直接后代）
declare type descendants = '/..';           // 后代节点
declare type self = '.';                    // 自身/兄弟节点
declare type root = '/';                    // 根节点

/**
 * 寻找指定节点
 * @param name 节点名称
 * @param from 从指定节点或组件开始寻找
 * @param relative 相对路径符
 * @param paths 寻找路径罗列
 * @return 返回搜到的节点
 * @example
 * ```
 * // 场景根节点 [Root]
 * findNode('Root');
 * findNode('Root', this.node, '/');
 * 
 * // 场景指定路径节点 Root->Parent->[Son]
 * findNode('Son', 'Root', 'Parent');
 * 
 * // 兄弟节点 Self->Parent->[Brother]
 * findNode('Brother', this.node, '.'); // = this.node.parent.getChildByName('Brother');
 * 
 * // 儿子节点 Self->['Son']
 * findNode('Son', this.node, '/.'); // = this.node.getChildByName('Son');
 * findNode('Daughter', this.node); // = this.node.getChildByName('Daughter');
 * 
 * // 孙子节点 Self->Son->Grandson->[GreatGrandson]
 * findNode('GreatGrandson', this.node, 'Son', 'Grandson', [...]);
 * 
 * // 子嗣节点 Self->Son->...->['Descendant']
 * findNode('Descendant', this.node, '/..'); // 向下递归直到直到
 * 
 * // 祖先节点 Self->Parent->...->['Ancestor']
 * findNode('Ancestor', this.node, '..'); // 向上回溯直到找到
 * ```
*/
export function findNode(name: string, ...paths: string[]): Node | null;
export function findNode(name: string, from: Node | Component, relative: relative): Node | null;
export function findNode(name: string, from: Node | Component, ...paths: string[]): Node | null;
export function findNode(name: string, arg?: Node | Component | string, ...rests: string[]): Node | null {
    const scene = director.getScene();
    if (isNullish(arg) || arg === '/') {
        return scene.getChildByName(name);
    } else if (isStr(arg)) {
        return scene.getChildByPath([arg, ...rests, name].join('/'));
    } else {
        let node = arg instanceof Node ? arg : arg.node;
        if (!node) return null;
        const relative = rests.head;
        if (relative === '/') {
            return scene.getChildByName(name);
        } else if (relative === '.') {
            return node.parent?.getChildByName(name) ?? null;
        } else if (relative === '/.' || relative === undefined) {
            return node.getChildByName(name);
        } else if (relative === '/..') {
            return _recurseName(name, [node]);
        } else if (relative === '..') {
            while (!!node.parent) {
                node = node.parent;
                if (node.name === name) return node;
            }
            return null;
        } else {
            return find([...rests, name].join('/'), node);
        }
    }
}

/**
 * 寻找指定组件
 * @param Comp 组件类型
 * @param from 从指定节点或组件开始寻找
 * @param relative 相对路径符
 * @param paths 寻找路径罗列
 * @return 返回搜到的组件
 * @example
 * ```
 * // 场景根节点 [Root]
 * findNode('Root');
 * findNode('Root', this.node, '/');
 * 
 * // 场景指定路径节点 Root->Parent->[Son]
 * findNode('Son', 'Root', 'Parent');
 * 
 * // 兄弟节点 Self->Parent->[Brother]
 * findNode('Brother', this.node, '.'); // = this.node.parent.getChildByName('Brother');
 * 
 * // 儿子节点 Self->['Son']
 * findNode('Son', this.node, '/.'); // = this.node.getChildByName('Son');
 * findNode('Daughter', this.node); // = this.node.getChildByName('Daughter');
 * 
 * // 孙子节点 Self->Son->Grandson->[GreatGrandson]
 * findNode('GreatGrandson', this.node, 'Son', 'Grandson', [...]);
 * 
 * // 子嗣节点 Self->Son->...->['Descendant']
 * findNode('Descendant', this.node, '/..'); // 向下递归直到直到
 * 
 * // 祖先节点 Self->Parent->...->['Ancestor']
 * findNode('Ancestor', this.node, '..'); // 向上回溯直到找到
 * ```
*/
export function findComp<T extends Component>(Comp: ConcreteClass<T>, ...paths: string[]): T | null;
export function findComp<T extends Component>(Comp: ConcreteClass<T>, from: Node | Component, relative: relative): T | null;
export function findComp<T extends Component>(Comp: ConcreteClass<T>, from: Node | Component, ...paths: string[]): T | null;
export function findComp<T extends Component>(Comp: ConcreteClass<T>, arg?: Node | Component | string, ...rests: string[]): T | null {
    const scene = director.getScene();
    if (isNullish(arg) || arg === '/') {
        return scene.getComponentInChildren(Comp);
    } else if (isStr(arg)) {
        return scene.getChildByPath([arg, ...rests].join('/'))?.getComponent(Comp);
    } else {
        let node = arg instanceof Node ? arg : arg.node;
        if (!node) return null;
        const relative = rests.head;
        if (relative === '/') {
            return scene.getComponentInChildren(Comp);
        } else if (relative === '.') {
            return node.parent?.getComponentInChildren(Comp);
        } else if (relative === '/.' || relative === undefined) {
            return node.getComponentInChildren(Comp);
        } else if (relative === '/..') {
            return _recurseComp(Comp, [node]);
        } else if (relative === '..') {
            while (!!node.parent) {
                node = node.parent;
                const comp = node.getComponent(Comp);
                if (!!comp) return comp;
            }
            return null;
        } else {
            node = find(rests.join('/'), node);
            return !!node ? node.getComponent(Comp) : null;
        }
    }
}


/**
 * 寻找场景中的主摄相机
 * @param paths 主相机所在路径
 * @return 寻找到的主摄像相机
 */
export function findCamera(...paths: string[]): Camera | null {
    if (notEmptyArr(paths)) {
        return findComp(Camera, ...paths);
    }
    return findComp(Camera, 'Main Camera')
        ?? director.getScene().getComponentInChildren(Camera) ?? null;
}

/**
 * 寻找场景中的主摄相机
 * @param paths 主相机所在路径
 * @return 寻找到的主摄像相机
 */
export function findUICamera(...paths: string[]): Camera | null {
    if (notEmptyArr(paths)) {
        return findComp(Camera, ...paths);
    }
    return findComp(Camera, 'Canvas', 'UI Camera')
        ?? director.getScene().getComponentInChildren(Canvas)?.getComponentInChildren(Camera) ?? null;
}

export function findEditorCamera(next?: Handler): renderer.scene.Camera | null {
    for (let scene of director.root.scenes) {
        for (let camera of scene.cameras) {
            if (camera.name === 'Editor Camera') {
                Function.invoke(next, null, camera);
                return camera;
            }
        }
    }
    return null;
}

export function findEditorUIGizmoCamera(next?: Handler): renderer.scene.Camera | null {
    for (let scene of director.root.scenes) {
        for (let camera of scene.cameras) {
            if (camera.name === 'Editor UIGizmoCamera') {
                Function.invoke(next, null, camera);
                return camera;
            }
        }
    }
    return null;
}

function _recurseName(name: string, nodes: Node[]): Node | null {
    const update: Node[] = [];
    for (let node of nodes) {
        const find = node.getChildByName(name);
        if (!find) return find;
        update.push(...node.children);
    }
    return _recurseName(name, update);
}


function _recurseComp<T extends Component>(Comp: ConcreteClass<T>, nodes: Node[]): T | null {
    const update: Node[] = [];
    for (let node of nodes) {
        const find = node.getComponentInChildren(Comp);
        if (!find) return find;
        update.push(...node.children);
    }
    return _recurseComp(Comp, update);
}